package decode;

import gui.GUI;

import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.ArrayList;

import javax.imageio.ImageIO;

import structure.Area;
import structure.Sequence;
import structure.Square;
import structure.VSeries;
import utilities.rle2dUtilities;

public class DecodeService implements Runnable {
	private int width;
	private int heigth;
	
	private int redSquaresCount;
	private int greenSquaresCount;
	private int blueSquaresCount;
	
	private int redVSeriesCount;
	private int greenVSeriesCount;
	private int blueVSeriesCount;
	
	private int redSequenceSize;
	private int greenSequenceSize;
	private int blueSequenceSize;
	
	private int byteSize;
	
	private ArrayList<Square> redSquares;
	private ArrayList<Square> greenSquares;
	private ArrayList<Square> blueSquares;
	
	private ArrayList<VSeries> redVSeries;
	private ArrayList<VSeries> greenVSeries;
	private ArrayList<VSeries> blueVSeries;
	
	private ArrayList<Byte> redSequence;
	private ArrayList<Byte> greenSequence;
	private ArrayList<Byte> blueSequence;
	
	private int hSeriesRedMarker;
	private int hSeriesGreenMarker;
	private int hSeriesBlueMarker;
	
	private byte [][] redBitmap;
	private byte [][] blueBitmap;
	private byte [][] greenBitmap;
	
	private FileInputStream fis;
	
	private GUI gui;
	
	private int rleType;
	
	private int[] redPcxMap;
	private int[] greenPcxMap;
	private int[] bluePcxMap;
	private File outputFile;
	private File inputFile;
	
	public DecodeService(File file,GUI gui, File outputFile) {
		this.inputFile = file;
		this.outputFile = outputFile;
		this.gui = gui;
		this.redPcxMap = new int[256];
		this.greenPcxMap = new int[256];
		this.bluePcxMap = new int[256];
		File decoded = new File("./temp/huffDecode.tmp");
		HuffmanDecode decoder = new HuffmanDecode(file, decoded);
		decoder.decode();
		try {
			fis = new FileInputStream(decoded);
		}
		catch (Exception exc) {
			System.out.println("Nie wybrano pliku!");
			exc.printStackTrace();
		}
		
		redSquares = new ArrayList<Square>();
		greenSquares = new ArrayList<Square>();
		blueSquares = new ArrayList<Square>();
		
		redVSeries = new ArrayList<VSeries>();
		greenVSeries = new ArrayList<VSeries>();
		blueVSeries = new ArrayList<VSeries>();
		
		redSequence = new ArrayList<Byte>();
		greenSequence = new ArrayList<Byte>();
		blueSequence = new ArrayList<Byte>();
		
		getHeader();
		
		redBitmap = new byte[this.heigth][this.width];
		greenBitmap = new byte[this.heigth][this.width];
		blueBitmap = new byte[this.heigth][this.width];
		
		new Thread(this).start();
	}

	public GUI getGui() {
		return gui;
	}

	public void setGui(GUI gui) {
		this.gui = gui;
	}
	
	public int getThreeBytesValue(byte fst, byte sec, byte thr) {
		
		Byte fstByte = new Byte(fst);
		Byte secByte = new Byte(sec);
		Byte thrByte = new Byte(thr);
				
		int fstIntVal = fstByte.intValue() & 0xFF;
		int secIntVal = secByte.intValue() & 0xFF;
		int thrIntVal = thrByte.intValue() & 0xFF;
		
		return fstIntVal|(secIntVal<<8)|(thrIntVal<<16);
	}
	
	public int getTwoBytesValue(byte fst, byte sec) {
		Byte fstByte = new Byte(fst);
		Byte secByte = new Byte(sec);
				
		int fstIntVal = fstByte.intValue() & 0xFF;
		int secIntVal = secByte.intValue() & 0xFF;
		
		return fstIntVal|(secIntVal<<8);
	}
	
	public int getOneByteValue(byte fst) {
		Byte fstByte = new Byte(fst);
		int fstIntVal = fstByte.intValue() & 0xFF;
		return fstIntVal;
	}
	
	public void getHeader() {
		byte[] rleByte = new byte[1];
		byte[] bytes = new byte[28];
		
		
		try {
			
			//get rle type
			
			fis.read(rleByte,0,1);
			this.rleType = getOneByteValue(rleByte[0]);
			
			//get dimensions
			fis.read(bytes,0,25);
			if(bytes != null) {
				this.width = getTwoBytesValue(bytes[0], bytes[1]);
				this.heigth = getTwoBytesValue(bytes[2], bytes[3]);
			
				if (this.width > 255 || this.heigth > 255)
					this.byteSize = 2;
				else
					this.byteSize = 1;
				
				this.redSquaresCount = getTwoBytesValue(bytes[4],bytes[5]);
				this.redVSeriesCount = getTwoBytesValue(bytes[6],bytes[7]);
					
				this.greenSquaresCount = getTwoBytesValue(bytes[8], bytes[9]);
				this.greenVSeriesCount = getTwoBytesValue(bytes[10],bytes[11]);
					
				this.blueSquaresCount = getTwoBytesValue(bytes[12],bytes[13]);
				this.blueVSeriesCount = getTwoBytesValue(bytes[14],bytes[15]);
				
				this.redSequenceSize = getThreeBytesValue(bytes[16], bytes[17], bytes[18]);
				
				this.greenSequenceSize = getThreeBytesValue(bytes[19], bytes[20], bytes[21]);
				
				this.blueSequenceSize = getThreeBytesValue(bytes[22], bytes[23], bytes[24]);
			}
			
			if(this.rleType == rle2dUtilities.PCX_RLE) {
				byte[] mapBytes = new byte[256];
				fis.read(mapBytes, 0, 256);
				for(int i=0;i<256;i++) {
					redPcxMap[i] = getOneByteValue(mapBytes[i]);
				}
				fis.read(mapBytes, 0, 256);
				for(int i=0;i<256;i++) {
					greenPcxMap[i] = getOneByteValue(mapBytes[i]);
				}
				fis.read(mapBytes, 0, 256);
				for(int i=0;i<256;i++) {
					bluePcxMap[i] = getOneByteValue(mapBytes[i]);
				}
				
			}
			else {
				byte[] markersBytes = new byte[3];
				fis.read(markersBytes,0,3);
				this.hSeriesRedMarker = getOneByteValue(markersBytes[0]);
				rle2dUtilities.H_SERIES_RED_MARKER = this.hSeriesRedMarker;
				
				this.hSeriesGreenMarker = getOneByteValue(markersBytes[1]);
				rle2dUtilities.H_SERIES_GREEN_MARKER = this.hSeriesGreenMarker;
				
				this.hSeriesBlueMarker = getOneByteValue(markersBytes[2]);
				rle2dUtilities.H_SERIES_BLUE_MARKER = this.hSeriesBlueMarker;
			}
			
			
		}
		catch (Exception exc) {
			System.out.println("Nie udalo sie odczytac danych naglowka!");
			exc.printStackTrace();
		}
	}
	
	public void getSquares(String COLOR_NAME){
		
		int count = 0;
		
		if(rle2dUtilities.RED_NAME.equals(COLOR_NAME)) {
			count = redSquaresCount;
		} else if (rle2dUtilities.GREEN_NAME.equals(COLOR_NAME)) {
			count = greenSquaresCount;
		} else if (rle2dUtilities.BLUE_NAME.equals(COLOR_NAME)) {
			count = blueSquaresCount;
		}
		
		try {
		
			for (int i=0;i<count;i++){
				byte[] sqrRep = new byte[2*this.byteSize+2];
				
				int x = -1;
				int y = -1;
				int a = -1;
				int color = -1;
				
				fis.read(sqrRep,0,2*this.byteSize+2);
				
				if (this.byteSize==1) {
					
					x = getOneByteValue(sqrRep[0]);
					y = getOneByteValue(sqrRep[1]);
					
					a = getOneByteValue(sqrRep[2]);
					
					color = getOneByteValue(sqrRep[3]);
				} else if (this.byteSize==2){
					
					x = getTwoBytesValue(sqrRep[0], sqrRep[1]);
					// Y
					y = getTwoBytesValue(sqrRep[2], sqrRep[3]);
					// A
					a = getOneByteValue(sqrRep[4]);
					//COLOR
					color = getOneByteValue(sqrRep[5]); 
				}
				
				Square square = new Square(a,x,y,color,byteSize);
				
				if(rle2dUtilities.RED_NAME.equals(COLOR_NAME)) {
					redSquares.add(square);
				} else if (rle2dUtilities.GREEN_NAME.equals(COLOR_NAME)) {
					greenSquares.add(square);
				} else if (rle2dUtilities.BLUE_NAME.equals(COLOR_NAME)) {
					blueSquares.add(square);
				}
			}
		}
		catch (Exception exc) {
			System.out.println("Nie udalo sie odczytac informacji o kwadratach!");
			exc.printStackTrace();
		}
	}
	
	public void getVSeries(String COLOR_NAME) {
		
		int count = 0;
		
		if(rle2dUtilities.RED_NAME.equals(COLOR_NAME)) {
			count = redVSeriesCount;
		} else if (rle2dUtilities.GREEN_NAME.equals(COLOR_NAME)) {
			count = greenVSeriesCount;
		} else if (rle2dUtilities.BLUE_NAME.equals(COLOR_NAME)) {
			count = blueVSeriesCount;
		}
		
		try {
			
			for (int i=0;i<count;i++){
				byte[] vsrRep = new byte[2*this.byteSize+2];
				
				int x = -1;
				int y = -1;
				int len = -1;
				int color = -1;
				
				fis.read(vsrRep,0,2*this.byteSize+2);
				
				if (this.byteSize==1) {
					
					x = getOneByteValue(vsrRep[0]);
					y = getOneByteValue(vsrRep[1]);
					
					len = getOneByteValue(vsrRep[2]);
					
					color = getOneByteValue(vsrRep[3]);
				} else if (this.byteSize==2){
					
					x = getTwoBytesValue(vsrRep[0], vsrRep[1]);
					// Y
					y = getTwoBytesValue(vsrRep[2], vsrRep[3]);
					// LEN
					len = getOneByteValue(vsrRep[4]);
					//COLOR
					color = getOneByteValue(vsrRep[5]); 
				}
				
				VSeries vseries = new VSeries(x,y,len,color,byteSize);
				
				if(rle2dUtilities.RED_NAME.equals(COLOR_NAME)) {
					redVSeries.add(vseries);
				} else if (rle2dUtilities.GREEN_NAME.equals(COLOR_NAME)) {
					greenVSeries.add(vseries);
				} else if (rle2dUtilities.BLUE_NAME.equals(COLOR_NAME)) {
					blueVSeries.add(vseries);
				}
			}
		}
		catch (Exception exc) {
			System.out.println("Nie udalo sie odczytac informacji o kwadratach!");
			exc.printStackTrace();
		}
	}
	
	public void getSequence(String COLOR_NAME){
		int count = 0;
		if(rle2dUtilities.RED_NAME.equals(COLOR_NAME)) {
			count = this.redSequenceSize;
		} else if (rle2dUtilities.GREEN_NAME.equals(COLOR_NAME)) {
			count = this.greenSequenceSize;
		} else if (rle2dUtilities.BLUE_NAME.equals(COLOR_NAME)) {
			count = this.blueSequenceSize;
		}
		
		try {
			for(int i=0;i<count;i++) {
				byte[] pixelRep = new byte[1];
				byte val = -1;
				
				fis.read(pixelRep, 0, 1);
				
				val = pixelRep[0];
				
				if(rle2dUtilities.RED_NAME.equals(COLOR_NAME)) {
					redSequence.add(val);
				} else if (rle2dUtilities.GREEN_NAME.equals(COLOR_NAME)) {
					greenSequence.add(val);
				} else if (rle2dUtilities.BLUE_NAME.equals(COLOR_NAME)) {
					blueSequence.add(val);
				}
				
			}
			
			if(rle2dUtilities.RED_NAME.equals(COLOR_NAME)) {
				redSequence.add((byte)(-1));
			} else if (rle2dUtilities.GREEN_NAME.equals(COLOR_NAME)) {
				greenSequence.add((byte)(-1));
			} else if (rle2dUtilities.BLUE_NAME.equals(COLOR_NAME)) {
				blueSequence.add((byte)(-1));
			}
		}
		catch (Exception exc) {
			System.out.println("Nie udalo sie odczytac informacji na temat sekwencji");
			exc.printStackTrace();
		}
	}
	
	public void drawRed (BufferedImage bimage){
		int sqrCov = 0;
		int serCov = 0;
		
			for (int aInx=0;aInx<redSquares.size();aInx++) {
				Area area = redSquares.get(aInx);		
				if(rle2dUtilities.SQUARE.equals(area.getKind())) {
					Square square = (Square) area;
					for(int i=square.getY();i<Math.min(square.getY()+square.getA(),this.heigth);i++){
						for(int j=square.getX();j<Math.min(square.getX()+square.getA(),this.width);j++){
							int color = bimage.getRGB(j, i) | square.getColor() << 16;
							bimage.setRGB(j, i, color);;
							redBitmap[i][j] = 1;
							sqrCov++;
						}
					}
				}
			}
			for (int aInx=0;aInx<redVSeries.size();aInx++) {
				Area area = redVSeries.get(aInx);
				if (rle2dUtilities.VSERIES.equals(area.getKind())) {
					VSeries vseries = (VSeries) area;
					for (int i=vseries.getY();i<vseries.getY()+vseries.getLength();i++) {
						int color = bimage.getRGB(vseries.getX(), i) | vseries.getColor() << 16;
						bimage.setRGB(vseries.getX(),i,color);
						redBitmap[i][vseries.getX()] = 1;
						serCov++;
					}
				}
			}
			System.out.println("Dekododowanie czerwone kwadraty "+sqrCov);
			System.out.println("Dekododowanie czerwone pionowe linie "+serCov);
		}
	
	public void drawBlue (BufferedImage bimage){
		for (int aInx=0;aInx<blueSquares.size();aInx++) {
			Area area = blueSquares.get(aInx);		
			if(rle2dUtilities.SQUARE.equals(area.getKind())) {
				Square square = (Square) area;
				for(int i=square.getY();i<Math.min(square.getY()+square.getA(),this.heigth);i++){
					for(int j=square.getX();j<Math.min(square.getX()+square.getA(),this.width);j++){
						int color = bimage.getRGB(j, i) | square.getColor();
						bimage.setRGB(j, i, color);
						blueBitmap[i][j] = 1;
					}
				}
			}
		}
		for (int aInx=0;aInx<blueVSeries.size();aInx++) {
			Area area = blueVSeries.get(aInx);
			VSeries vseries = (VSeries) area;
			for (int i=vseries.getY();i<vseries.getY()+vseries.getLength();i++) {
				int color = bimage.getRGB(vseries.getX(), i) | vseries.getColor();
				bimage.setRGB(vseries.getX(),i,color);
				blueBitmap[i][vseries.getX()] = 1;
			}
		}
	}
	/**
	 * 
	 * @param areas
	 */
	public void drawGreen (BufferedImage bimage){
		for (int aInx=0;aInx<greenSquares.size();aInx++) {
			Area area = greenSquares.get(aInx);		
			if(rle2dUtilities.SQUARE.equals(area.getKind())) {
				Square square = (Square) area;
				for(int i=square.getY();i<Math.min(square.getY()+square.getA(),this.heigth);i++){
					for(int j=square.getX();j<Math.min(square.getX()+square.getA(),this.width);j++){
						int color = bimage.getRGB(j, i) | square.getColor() << 8;
						bimage.setRGB(j, i, color);
						greenBitmap[i][j] = 1;
					}
				}
			}
		}
		for (int aInx=0;aInx<greenVSeries.size();aInx++) {
			Area area = greenVSeries.get(aInx);
			VSeries vseries = (VSeries) area;
			for (int i=vseries.getY();i<vseries.getY()+vseries.getLength();i++) {
				int color = bimage.getRGB(vseries.getX(), i) | vseries.getColor() << 8;
				bimage.setRGB(vseries.getX(),i,color);
				greenBitmap[i][vseries.getX()] = 1;
			}
		}
	}
	
	public void fillFreeGaps(String COLOR_NAME,BufferedImage bimage) {
			
		byte [][] bitmap = null;
		ArrayList<Byte> colorSeq = null;
		int marker = -1;
		int[] pcxMap = null;
		
		if(rle2dUtilities.RED_NAME.equals(COLOR_NAME)) {
			bitmap = redBitmap;
			colorSeq = redSequence;
			marker = rle2dUtilities.H_SERIES_RED_MARKER;
			pcxMap = redPcxMap;
		}
		else if (rle2dUtilities.BLUE_NAME.equals(COLOR_NAME)) {
			bitmap = blueBitmap;
			colorSeq = blueSequence;
			marker = rle2dUtilities.H_SERIES_BLUE_MARKER;
			pcxMap = bluePcxMap;
		}
		else if (rle2dUtilities.GREEN_NAME.equals(COLOR_NAME)) {
			bitmap = greenBitmap;
			colorSeq = greenSequence;
			marker = rle2dUtilities.H_SERIES_GREEN_MARKER;
			pcxMap = greenPcxMap;
		}
		int puste = 0;
//		for (int i=0;i<this.heigth;i++) {
//			for(int j=0;j<this.width;j++) {
//				if (bitmap[i][j] == 0) {
//					puste ++;
//				}
//			}
//		}
//		System.out.println("OOOOO pustych: "+puste);
		Sequence seq = null;
		if(this.rleType == rle2dUtilities.CUSTOM_RLE)
			seq = new Sequence(colorSeq,marker);
		else
			seq = new Sequence(colorSeq,pcxMap);
//		System.out.println("OOOOO zapelniaczy = "+seq.countCapacity());
		for (int i=0;i<this.heigth;i++) {
			for(int j=0;j<this.width;j++) {
				if (bitmap[i][j] == 0) {
					Integer color = new Integer(getOneByteValue(seq.getNextVal()));
					if (rle2dUtilities.RED_NAME.equals(COLOR_NAME))
						bimage.setRGB(j, i, bimage.getRGB(j, i)| color.intValue() << 16);
					else if (rle2dUtilities.GREEN_NAME.equals(COLOR_NAME))
						bimage.setRGB(j, i, bimage.getRGB(j, i)| color.intValue() << 8);
					else if (rle2dUtilities.BLUE_NAME.equals(COLOR_NAME))
						bimage.setRGB(j, i, bimage.getRGB(j, i)| color.intValue());
					bitmap[i][j] = 1;
				}
			}
		}
		getClass();
	}
	
	public BufferedImage createBufferedImage (){
		BufferedImage bimage = new BufferedImage(this.width, this.heigth, BufferedImage.TYPE_INT_RGB);
		return bimage;
	}
	
	private String constructOutputFileName(){
		if (inputFile != null && inputFile.isFile()){
			String inputName = inputFile.getName();
			if (inputName.endsWith(".rle2d")){
				return (inputName.substring(0, inputName.length()-6)+".bmp");
			}
		}
		return "Final.bmp";
	}

	@Override
	public void run() {
		
		getGui().initProces();
		
		//getHeader();
		
		BufferedImage bimage = createBufferedImage();
		
		getSquares(rle2dUtilities.RED_NAME);
		getVSeries(rle2dUtilities.RED_NAME);
		
		BufferedImage redImg = createBufferedImage();
		drawRed(redImg);
		
		getGui().endSelectRedBlocks(redImg);
		
		getSquares(rle2dUtilities.GREEN_NAME);
		getVSeries(rle2dUtilities.GREEN_NAME);
		
		BufferedImage greenImg = createBufferedImage();
		drawGreen(greenImg);
		
		getGui().endSelectGreenBlocks(greenImg);
		
		getSquares(rle2dUtilities.BLUE_NAME);
		getVSeries(rle2dUtilities.BLUE_NAME);
		
		BufferedImage blueImg = createBufferedImage();
		drawBlue(blueImg);
		
		getGui().endSelectBlueBlocks(blueImg);
		
		getSequence(rle2dUtilities.RED_NAME);
		
		getSequence(rle2dUtilities.GREEN_NAME);
		
		getSequence(rle2dUtilities.BLUE_NAME);
		
		getGui().endEncodeHSeries();
		//getGui().addReportPanel(null);
		
		drawRed(bimage);
		drawGreen(bimage);
		drawBlue(bimage);
		
		fillFreeGaps(rle2dUtilities.RED_NAME, bimage);
		fillFreeGaps(rle2dUtilities.GREEN_NAME, bimage);
		fillFreeGaps(rle2dUtilities.BLUE_NAME, bimage);
		
		
		
		File file = new File("./temp/Final.bmp");
		//File output = getGui().getDecompressedOutputFile();
		try{
			file = new File("./temp/"+constructOutputFileName());
			if (file != null){
				ImageIO.write(bimage, "bmp", file);
			}
//			file = new File("RED.bmp");
//			ImageIO.write(redImg, "bmp", file);
//			file = new File("GREEN.bmp");
//			ImageIO.write(greenImg, "bmp", file);
//			file = new File("BLUE.bmp");
//			ImageIO.write(blueImg, "bmp", file);
		} catch (IOException e) {
			e.printStackTrace();
		}
		getGui().addSavableFinalPicture(bimage, file, constructOutputFileName());
		
	}
	
	

}
